/*

Copyright (c) 2012, Fabian Woltermann <fwoltermann@gmail.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: 

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer. 
2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution. 

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/

#include "InFAbServer.h"

inline bool Server::SendRCV() {
	char command = Server::CMD_TRS_RCV;

	int rc = send(ConnectSocket, &command, sizeof(char), 0);
	if (rc == -1 ) {
		//scream
		return false;
	}

	return true;
}

inline bool Server::GetRCV() {
	char buf;
	int rc = recv(ConnectSocket, &buf, sizeof(char), 0);
	if (rc == -1)
		return false;
	
	if (buf == Server::CMD_ERR) {
		netexception* e = new netexception(__FILE__, __LINE__);
		throw(e);
	}

	return buf == Server::CMD_TRS_RCV;
}

Server::Server() {
	memset(&Address, 0, sizeof(addrinfo)); 
	ConnectSocket = INVALID_SOCKET;

	prog = new wxProgressDialog("Netzwerkzugriff", "Verbindung wird aufgebaut", 100, nullptr, wxPD_SMOOTH);

	prog->Show();
}

Server::~Server() {
	prog->Close();
	prog->Destroy();
	delete prog;
}

bool Server::Connect(string address, string port) {
	wxString message = "Verbinde zum Server bei ";
	message += address.c_str();
	message += ":";
	message += port.c_str();

	prog->Update(0, message);

	addrinfo hints;
	addrinfo *results;
	memset(&hints, 0, sizeof(addrinfo));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE;

	WSADATA wsaData;

	int err = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (err != 0) {
		prog->Destroy();
		return false;
	}

	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
		prog->Destroy();
		return false;
	}

	err = getaddrinfo(address.c_str(), port.c_str(), &hints, &results);
	if (err != 0) {
		err = WSAGetLastError();
		WSACleanup();
		prog->Destroy();
		return false;
	}

	if (results == nullptr) {
	    WSACleanup();
		prog->Destroy();
		return false;
	}

	for (auto p = results; p != nullptr; p = p->ai_next) {
		ConnectSocket = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
		if (ConnectSocket == INVALID_SOCKET)
			continue;
		else {
			Address = *p;
			break;
		}
	}

	int result = connect(ConnectSocket, Address.ai_addr, Address.ai_addrlen);

	if (result == SOCKET_ERROR) {
	    closesocket(ConnectSocket);
		ConnectSocket = INVALID_SOCKET;
		WSACleanup();
		prog->Destroy();
		return false;
	}

	message = "Verbindung hergestellt";
	prog->Update(0, message);

	char command = Server::CMD_TRS_BGN;
	send(ConnectSocket, &command, sizeof(char), 0);

	if (!GetRCV()) {
		prog->Destroy();
		return false;
	}

	return true;
}

void Server::Disconnect() {
	if (ConnectSocket == INVALID_SOCKET)
		return;

	closesocket(ConnectSocket);
	WSACleanup();
}

bool Server::GetNameList(vector<string>* NameListPointer, string department, string* error) {
	char command;
	command = Server::CMD_GET_Names;

	if (ConnectSocket == INVALID_SOCKET)
		return false;

	int rc = send(ConnectSocket, &command, sizeof(char), 0);
	if (rc == -1 ) {
		//scream
		return false;
	}

	try {
		if (GetRCV()) {
			rc = send(ConnectSocket, department.c_str(), department.length(), 0);
			if (rc == -1 ) {
				//scream
				return false;
			}

			if (GetRCV()) {
				//How many records can we expect?
				char rnum;
				rc = recv(ConnectSocket, &rnum, sizeof(char), 0);
				if (rc <= 0) {
					//scream
					return false;
				}

				SendRCV();

				NameListPointer->clear();

				float progress = 100.0f / rnum;
	
				for (int i = 0; i < (int)rnum; ++i) {
					char temp[512];
					string name;
					rc = recv(ConnectSocket, temp, sizeof(temp), 0);
		
					SendRCV();

					for (int j = 0; j < rc; ++j) {
						name.push_back(temp[j]);
					}

					NameListPointer->push_back(name);

					wxString message = "Empfange paket: ";
					message << i << " // " << rnum;
					prog->Update(progress * i, message);
				}

				command = Server::CMD_TRS_END;
				int rc = send(ConnectSocket, &command, sizeof(char), 0);
				if (rc == -1 ) {
					//scream
					return false;
				}

				if (!GetRCV()) {
					return false;
				}
			}
		}
	} catch(netexception& e) {
		*error = e.what();
		return false;
	}

	error->clear();
	return true;
}

bool Server::SetNameList(vector<Worker>* WorkerListPointer, string* error) {
	char command = Server::CMD_SET_Names;

	int rc = send(ConnectSocket, &command, sizeof(char), 0);
	if (rc == -1 ) {
		//scream
		return false;
	}

	try {
		if (GetRCV()) {
			size_t num = WorkerListPointer->size();
			rc = send(ConnectSocket, (const char*)&num, sizeof(size_t), 0);
	
			if (rc == -1 ) {
				//scream
				return false;
			}

			float progress = 100.0f / WorkerListPointer->size();

			if (GetRCV()) {
				for (auto worker = WorkerListPointer->begin(); worker != WorkerListPointer->end(); ++worker) {
					size_t size = worker->GetName().size();

					rc = send(ConnectSocket, worker->GetName().c_str(), size, 0);
					if (rc == -1 ) {
						//scream
						return false;
					}

					if (!GetRCV())
						return false;

					size = worker->GetDepartment().size();
					rc = send(ConnectSocket, worker->GetDepartment().c_str(), size, 0);
					if (rc == -1 ) {
						//scream
						return false;
					}

					if (!GetRCV())
						return false;

					char salary[8];
					sprintf_s(salary, "%5.2f",  worker->GetSalary());
					if (rc == -1 ) {
						//scream
						return false;
					}
					rc = send(ConnectSocket, salary, sizeof(salary), 0);

					if (!GetRCV())
						return false;

					wxString message = "Sende Datenpaket: ";
					message << (worker - WorkerListPointer->begin()) << " // " << WorkerListPointer->size();
					prog->Update(progress * (worker - WorkerListPointer->begin()), message);
				}

				command = Server::CMD_TRS_END;
				rc = send(ConnectSocket, &command, sizeof(char), 0);
				if (rc == -1 ) {
					//scream
					return false;
				}

				if (!GetRCV())
					return false;
			}
		}
	} catch (netexception& e) {
		*error = e.what();
		return false;
	}

	error->clear();
	return true;
}

bool Server::MarkMonthLocked(wxDateTime month, bool lock, string* error) {
	char command = Server::CMD_SET_MonthLock;

	if (ConnectSocket == INVALID_SOCKET)
		return false;

	int rc = send(ConnectSocket, &command, sizeof(char), 0);
	if (rc == -1 ) {
		//scream
		return false;
	}

	try {
		if (GetRCV()) {
			char data = month.GetMonth();
			rc = send(ConnectSocket, &data, sizeof(char), 0);
			if (rc == -1)
				return false;

			if (GetRCV()) {
				char result = lock ? 1 : 0;
				rc = send(ConnectSocket, &result, sizeof(char), 0);
				if (rc == -1)
					return false;

				if (GetRCV()) {
					command = Server::CMD_TRS_END;
					rc = send(ConnectSocket, &command, sizeof(char), 0);
					if (rc == -1)
						return false;

					if (GetRCV())
						return true;
				}
			}
		}
	} catch (netexception& e) {
		*error = e.what();
		return false;
	}

	return false;
}

bool Server::IsMonthLocked(wxDateTime month, bool* lock, string* error) {
	char command = Server::CMD_GET_MonthLock;

	int rc = send(ConnectSocket, &command, sizeof(char), 0);
	if (rc == -1 ) {
		//scream
		return false;
	}

	try {
		if (GetRCV()) {
			char data = month.GetMonth();
			rc = send(ConnectSocket, &data, sizeof(char), 0);

			if (GetRCV()) {
				rc = recv(ConnectSocket, &data, sizeof(char), 0);
				
				SendRCV();

				if (data == 1)
					*lock = true;
				else
					*lock = false;

				command = Server::CMD_TRS_END;
				rc = send(ConnectSocket, &command, sizeof(char), 0);

				if (!GetRCV()) {
					return false;
				}
			}
		}
	} catch (netexception& e) {
		*error = e.what();
		return false;
	}

	return true;
}

bool Server::GetOrderMap(unordered_map<string, string>* OrderMapPointer, string* error) {
	char command = Server::CMD_GET_OrderList;

	OrderMapPointer->clear();

	int rc = send(ConnectSocket, &command, sizeof(char), 0);
	if (rc == -1 ) {
		//scream
		return false;
	}

	try {
		if (GetRCV()) {
			char buffer[128];
			rc = recv(ConnectSocket, buffer, sizeof(buffer), 0);

			SendRCV();
			int n_records = atoi(buffer);

			float progress = 100.0f / n_records;

			for (int i = 0; i < n_records; ++i) {
				rc = recv(ConnectSocket, buffer, sizeof(buffer), 0);
				string key = buffer;

				SendRCV();

				rc = recv(ConnectSocket, buffer, sizeof(buffer), 0);
				string name = buffer;

				SendRCV();

				OrderMapPointer->insert(pair<string, string>(key, name));

				wxString message = "Empfange Datenpaket: ";
				message << i << " // " << n_records;
				prog->Update(progress * i, message);
			}
		}
	} catch (netexception& e) {
		*error = e.what();
		return false;
	}

	return true;
}

bool Server::SetOrderMap(unordered_map<string, string>* OrderMapPointer, string* error) {
	char command = Server::CMD_SET_OrderList;

	int rc = send(ConnectSocket, &command, sizeof(char), 0);
	if (rc == -1 ) {
		//scream
		return false;
	}

	try {
		if (GetRCV()) {
			char n_records[64];
			sprintf_s(n_records, "%d", OrderMapPointer->size());

			rc = send(ConnectSocket, n_records, sizeof(n_records), 0);

			if (GetRCV()) {
				
				float progress = 100.0f / OrderMapPointer->size();
				int pos = 0;
				for (auto order = OrderMapPointer->begin(); order != OrderMapPointer->end(); ++order) {
					rc = send(ConnectSocket, order->first.c_str(), order->first.size(), 0);

					if (!GetRCV())
						return false;

					rc = send(ConnectSocket, order->second.c_str(), order->second.size(), 0);

					if (!GetRCV())
						return false;

					wxString message = "Sende Datenpaket: ";
					
					message << pos << " // " << OrderMapPointer->size();
					prog->Update(progress * pos, message);
					++pos;
				}

				command = Server::CMD_TRS_END;
				rc = send(ConnectSocket, &command, sizeof(char), 0);

				if (!GetRCV())
						return false;
			}
		}
	} catch (netexception& e) {
		*error = e.what();
		return false;
	}

	return true;
}

bool Server::GetAssignments(wxDateTime month, Worker* worker, string* error) {
	char command = Server::CMD_GET_Assignment;

	int rc = send(ConnectSocket, &command, sizeof(char), 0);
	if (rc == -1 ) {
		//scream
		return false;
	}

	try {
		if (GetRCV()) {
			rc = send(ConnectSocket, worker->GetName().c_str(), worker->GetName().length(), 0);

			if (GetRCV()) {
				string date = month.Format();

				rc = send(ConnectSocket, date.c_str(), date.length(), 0);

				if (GetRCV()) {
					char n_assignments[4];

					rc = recv(ConnectSocket, n_assignments, sizeof(n_assignments), 0);

					int num = atoi(n_assignments);

					SendRCV();

					for (int i = 0; i < num; ++i) {
						char Key[128];

						rc = recv(ConnectSocket, Key, sizeof(Key), 0);
						SendRCV();

						Assignment* a = new Assignment(Key);

						char n_entries[4];
						rc = recv(ConnectSocket, n_entries, sizeof(n_entries), 0);
						int entries = atoi(n_entries);
						SendRCV();

						for (int j = 0; j < entries; ++j) {
							char rcvdate[128];

							rc = recv(ConnectSocket, rcvdate, sizeof(rcvdate), 0);
							wxDateTime time;
							time.ParseDate(rcvdate);
							SendRCV();

							char worktime[5];
							rc = recv(ConnectSocket, worktime, sizeof(worktime), 0);
							float wtime = atof(worktime);
							SendRCV();

							a->AddTime(time, wtime);
						}

						worker->AddAssignment(*a);
						delete a;
					}
				}
			}
		}
	} catch (netexception& e) {
		*error = e.what();
		return false;
	}

	return true;
}

bool Server::SetAssignments(string orderkey, map<wxDateTime, float>* hoursworked, string* error) {
	char command = Server::CMD_SET_Assignment;
	
	int rc = send(ConnectSocket, &command, sizeof(char), 0);
	if (rc == -1 ) {
		//scream
		return false;
	}

	try {
		if (GetRCV()) {
			rc = send(ConnectSocket, orderkey.c_str(), orderkey.length(), 0);

			if (GetRCV()) {
				char n_entries[32];
				sprintf_s(n_entries, "%d", hoursworked->size());

				rc = send(ConnectSocket, n_entries, sizeof(n_entries), 0);

				if (GetRCV()) {
					for (auto mapit = hoursworked->begin(); mapit != hoursworked->end(); ++mapit) {
						wxString date = mapit->first.Format();

						rc = send(ConnectSocket, date.c_str().AsChar(), date.length(), 0);

						GetRCV();

						char time[5];

						sprintf_s(time, "%2.2f", mapit->second);

						rc = send(ConnectSocket, time, sizeof(time), 0);

						GetRCV();
					}
				}

				command = Server::CMD_TRS_END;
				rc = send(ConnectSocket, &command, sizeof(char), 0);

				GetRCV();
			}
		}
	} catch (netexception& e) {
		*error = e.what();
		return false;
	}

	return true;
}